#include "mainwindow.h"
#include <QFileDialog>   // 用于文件保存对话框
#include <QMessageBox>   // 用于显示消息框
#include <QDataStream>   // 用于数据反序列化
#include <QHostAddress>  // 用于处理IP地址
#include <QFileInfo>     // 用于获取文件名

/**
 * @brief “开始接收”按钮点击事件处理槽函数。
 *
 * 配置UDP套接字以监听指定端口，准备接收文件。
 * 用户会被提示选择文件的保存位置。
 * 如果已在进行发送操作，会先清理发送状态。
 * 更新UI以反映接收状态。
 */
void MainWindow::on_btnReceive_clicked()
{
    quint16 port = editTargetPort->text().toUShort();
    if (port == 0) {
        QMessageBox::warning(this, "提示", "请输入有效的监听端口！");
        return;
    }
    if (sendingFileActive) cleanupSenderState(false); // 取消可能正在进行的发送

    cleanupReceiverState(); // 清理上一个接收会话的状态

    outputFilePath_R = QFileDialog::getSaveFileName(this, "选择保存接收文件的位置");
    if (outputFilePath_R.isEmpty()) {
        textLog->append("用户取消选择保存位置。");
        return;
    }

    if (udpSocket->state() == QAbstractSocket::BoundState) {
        if (udpSocket->localPort() == port) {
            textLog->append("已在端口 " + QString::number(port) + " 监听。重新开始接收...");
        } else {
            udpSocket->close();
        }
    }

    if (udpSocket->state() != QAbstractSocket::BoundState) {
        if (!udpSocket->bind(QHostAddress::Any, port, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) {
            textLog->append("端口 " + QString::number(port) + " 绑定失败: " + udpSocket->errorString());
            QMessageBox::critical(this, "错误", "端口绑定失败！\n" + udpSocket->errorString());
            return;
        }
    }

    textLog->append("已开始监听端口: " + QString::number(port));
    textLog->append("文件将保存到: " + outputFilePath_R);

    receivingFileActive = true;
    updateUiForActiveTransfer(true);

    expectedTotalPackets_R = 0;
    receivedPacketIndices_R.clear();
    progressBar->setValue(0);
    progressBar->setMaximum(1);
    labelFileSize->setText("文件大小: 等待数据...");
    labelTransferSpeed->setText("速度: -");
    if(speedUpdateTimer->isActive()) speedUpdateTimer->stop(); // 停止发送方的速度计时器
}

/**
 * @brief 处理接收到的数据包（接收端核心逻辑）。
 *
 * 此函数在UDP套接字接收到数据报且当前处于接收文件状态时被调用。
 * 它解析数据包头部（序列号、总包数），处理第一个包的初始化逻辑（创建文件、设置进度条），
 * 将数据写入文件的正确位置，并处理重复包。
 * 每收到一个有效数据包后，会向发送端回复一个ACK包。
 * 当所有数据包都接收完毕后，完成文件接收并清理状态。
 *
 * @param datagram 收到的原始数据报。
 * @param sender 发送该数据报的主机地址。
 * @param senderPort 发送该数据报的端口号。
 */
void MainWindow::handleReceiveData(const QByteArray &datagram, const QHostAddress &sender, quint16 senderPort)
{
    if (!receivingFileActive || outputFilePath_R.isEmpty()) {
        textLog->append("错误：接收到数据但接收状态未正确初始化或输出路径为空。");
        return;
    }

    QDataStream in(datagram);
    in.setVersion(QDataStream::Qt_5_15);

    if (datagram.size() < (2 * sizeof(quint32))) {
        textLog->append(QString("R: 收到过小的数据包 (大小: %1 bytes) 从 %2:%3").arg(datagram.size()).arg(sender.toString()).arg(senderPort));
        return;
    }

    quint32 seq, totalInPacket;
    in >> seq >> totalInPacket;

    if (in.status() != QDataStream::Ok) {
        textLog->append(QString("R: 数据包头解析错误从 %1:%2").arg(sender.toString()).arg(senderPort));
        return;
    }
    if (totalInPacket == 0) {
        textLog->append(QString("R: 收到总包数为0的无效数据包从 %1:%2").arg(sender.toString()).arg(senderPort));
        return;
    }

    if (expectedTotalPackets_R == 0) { // 此接收会话的第一个数据包
        expectedTotalPackets_R = totalInPacket;
        progressBar->setMaximum(expectedTotalPackets_R);
        textLog->append(QString("R: 开始接收文件，总包数: %1. 保存到: %2").arg(expectedTotalPackets_R).arg(outputFilePath_R));

        if (outputFile_R) { delete outputFile_R; outputFile_R = nullptr; }
        outputFile_R = new QFile(outputFilePath_R);
        if (!outputFile_R->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
            QMessageBox::critical(this, "接收错误", "无法打开保存文件进行写入: " + outputFilePath_R + "\n错误: " + outputFile_R->errorString());
            cleanupReceiverState(false, true); // 清理并尝试删除文件
            return;
        }
        textLog->append(QString("R: 输出文件 %1 已打开用于写入。").arg(outputFilePath_R));
    } else if (totalInPacket != expectedTotalPackets_R) {
        textLog->append(QString("R: 警告 - 数据包中的总包数 (%1) 与期望值 (%2) 不符。来自 %3:%4")
                            .arg(totalInPacket).arg(expectedTotalPackets_R).arg(sender.toString()).arg(senderPort));
    }

    QByteArray data = datagram.mid(2 * sizeof(quint32)); // 提取实际数据

    if (receivedPacketIndices_R.contains(seq)) { // 重复包
        textLog->append(QString("R: 收到重复的第 %1 包从 %2:%3，已忽略").arg(seq + 1).arg(sender.toString()).arg(senderPort));
    } else { // 新包
        if (!outputFile_R || !outputFile_R->isOpen()) {
            textLog->append(QString("R: 错误 - 尝试写入第 %1 包时输出文件未打开。").arg(seq + 1));
            cleanupReceiverState(false, true);
            return;
        }
        qint64 offset = (qint64)seq * packetSize;
        if (!outputFile_R->seek(offset)) {
            textLog->append(QString("R: 错误 - 无法定位到文件偏移量 %1 (包 %2). 错误: %3")
                                .arg(offset).arg(seq + 1).arg(outputFile_R->errorString()));
            cleanupReceiverState(false, true);
            return;
        }
        qint64 bytesWritten = outputFile_R->write(data);
        if (bytesWritten != data.size()) {
            textLog->append(QString("R: 错误 - 写入文件失败 (包 %1). 尝试写入 %2, 实际写入 %3. 错误: %4")
                                .arg(seq + 1).arg(data.size()).arg(bytesWritten).arg(outputFile_R->errorString()));
            cleanupReceiverState(false, true);
            return;
        }
        receivedPacketIndices_R.insert(seq);
        progressBar->setValue(receivedPacketIndices_R.size());
        textLog->append(QString("R: 收到并写入第 %1/%2 包 (数据大小: %3 bytes) 从 %4:%5")
                            .arg(seq + 1).arg(expectedTotalPackets_R).arg(data.size()).arg(sender.toString()).arg(senderPort));
        labelFileSize->setText(QString("接收进度: %1/%2 包").arg(receivedPacketIndices_R.size()).arg(expectedTotalPackets_R));
    }

    // 回复ACK
    QByteArray ackMessage = QString("ACK:%1").arg(seq).toUtf8();
    udpSocket->writeDatagram(ackMessage, sender, senderPort);

    // 检查是否接收完成
    if (expectedTotalPackets_R > 0 && receivedPacketIndices_R.size() == expectedTotalPackets_R) {
        textLog->append("R: 所有数据包接收完毕，文件组装完成。");
        qint64 finalSize = 0;
        if (outputFile_R && outputFile_R->isOpen()) {
            outputFile_R->flush();
            finalSize = outputFile_R->size();
        }
        cleanupReceiverState(true); // 成功完成
        QMessageBox::information(this, "成功", "文件接收并保存完成！\n最终大小: " + formatFileSize(finalSize));
        labelFileSize->setText(QString("已保存: %1").arg(formatFileSize(finalSize)));
    }
}